using System;
using System.Collections.Generic;
using System.Threading;
using Heaven.Engine.Concurrency;

namespace Heaven.Engine.Animation
{
    /// <summary>
    /// This is a collection of animations (timelines).
    /// Helps to manage a group of timelines
    /// </summary>
    public class Storyboard : GenericCollection<Timeline>
    {
        #region Fields

        // Shadow storyboard
        ShadowStoryboard shadowStoryboard = null;
        
        #endregion

        #region Properties

        /// <summary>
        /// Gets the current animation clock
        /// </summary>
        public Clock Clock
        {
            get { return shadowStoryboard.Clock; }
            set
            {
                shadowStoryboard.Clock = value;
            }
        }
        
        #endregion

        #region Initialization

        /// <summary>
        /// Default constructor
        /// </summary>
        public Storyboard()
        {
            shadowStoryboard = new ShadowStoryboard(new Clock());
        }

        #endregion

        #region Methods

        /// <summary>
        /// Adds an item
        /// </summary>
        /// <param name="timeline">Timeline</param>
        public override void Add(Timeline timeline)
        {
            base.Add(timeline);
            shadowStoryboard.Add(timeline);
            timeline.Invalidated += new EventHandler(OnTimelineInvalidated);
        }
        
        /// <summary>
        /// Removes the given item
        /// </summary>
        /// <param name="timeline">Timeline</param>
        public override void Remove(Timeline timeline)
        {
            timeline.Invalidated -= new EventHandler(OnTimelineInvalidated);
            base.Remove(timeline);
            shadowStoryboard.Remove(timeline);            
        }

        /// <summary>
        /// Begins animation
        /// </summary>
        public void Begin()
        {
            shadowStoryboard.Clock.Start();
        }

        /// <summary>
        /// Begins a particular timeline
        /// </summary>
        public void Begin(Timeline timeline)
        {
            timeline.BeginTime = Clock.Milliseconds;
            if (!Contains(timeline)) Add(timeline);
            shadowStoryboard.Invalidate();
            Clock.Resume();
        }

        #endregion

        #region Event handlers

        void OnTimelineInvalidated(object sender, EventArgs e)
        {
            shadowStoryboard.Invalidate();
        }

        #endregion
    }

    /// <summary>
    /// This is a collection of animations (timelines).
    /// Helps to manage a group of timelines
    /// </summary>
    class ShadowStoryboard : DeferredCollection<Timeline>
    {
        #region Static updating

        static List<StoryboardUpdateDelegate> storyboardTasks = new List<StoryboardUpdateDelegate>();
        static Thread tasksUpdatingThread = null;
        static ManualResetEvent taskAddedEvent = new ManualResetEvent(false);

        static void AddTask(StoryboardUpdateDelegate task)
        {
            lock (storyboardTasks)
            {
                if(!storyboardTasks.Contains(task))
                    storyboardTasks.Add(task);
            }
            if (tasksUpdatingThread == null)
            {
                // Initialize the thread
                tasksUpdatingThread = new Thread(TaskUpdating);
                tasksUpdatingThread.IsBackground = true;
                tasksUpdatingThread.Name = "Animation updating";
                tasksUpdatingThread.Start();
            }
            taskAddedEvent.Set();
        }

        static void TaskUpdating()
        {
            do
            {
                for (int i = storyboardTasks.Count - 1; i >= 0; i--)
                {
                    StoryboardUpdateDelegate task = storyboardTasks[i];
                    if (!task())
                    {
                        lock (storyboardTasks) { storyboardTasks.RemoveAt(i); }
                    }                    
                }
                
                if (storyboardTasks.Count == 0) taskAddedEvent.Reset();
                taskAddedEvent.WaitOne();

                // For CPU utilization
                Thread.Sleep(1);
            }
            while (true);
        }

        #endregion

        #region Fields

        // The current animation clock
        Clock clock = null;

        // Duration of the animation
        double endTime = Double.NaN;
        bool endTimeInvalidated = true;

        // When this event has fired async update has to be stopped
        bool stopRequired = false;
        // When this event has fired async update has to be continued
        bool processingRequired = false;



        #endregion

        #region Properties

        /// <summary>
        /// Gets the current animation clock
        /// </summary>
        public Clock Clock
        {
            get { return clock; }
            set
            {
                lock (this)
                {
                    if (clock != null)
                    {
                        clock.Started -= new EventHandler(OnClockStarted);
                        clock.Stopped -= new EventHandler(OnClockStopped);
                    }
                    clock = value;
                    clock.Started += new EventHandler(OnClockStarted);
                    clock.Stopped += new EventHandler(OnClockStopped);
                }
            }
        }
        
        #endregion

        #region Initialization

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="clock">Clock</param>
        public ShadowStoryboard(Clock clock)
        {
            Clock = clock;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Invalidate internal state
        /// </summary>
        public void Invalidate()
        {
            endTimeInvalidated = true;
            AddTask(Task);
        }

        /// <summary>
        /// Resumes async processing
        /// </summary>
        public void ResumeProcessing()
        {
            //processingRequired.Set();
            stopRequired = false;
            AddTask(Task);
        }

        /// <summary>
        /// Suspends async processing
        /// </summary>
        public void SuspendProcessing()
        {
            //processingRequired.Reset();
            //stopRequired.Set();
            stopRequired = true;
        }

        #endregion

        #region Clock Events

        void OnClockStopped(object sender, EventArgs e)
        {
            SuspendProcessing();
        }

        void OnClockStarted(object sender, EventArgs e)
        {
            ResumeProcessing();
        }

        #endregion

        #region Updating

        // Asynchronous update
        /*void Async()
        {
            while (processingRequired.WaitOne())
            {
                do
                {
                    if (IsChanged || endTimeInvalidated)
                    {
                        // Preform deferred actions
                        Perform();

                        // Update duration
                        endTime = 0;
                        foreach (Timeline timeline in this)
                        {
                            double currnet = timeline.Duration + timeline.BeginTime;
                            if (currnet > endTime) endTime = currnet;
                        }
                        endTimeInvalidated = false;
                    }

                    

                    Update();
                    if (stopRequired.WaitOne(1, false) == true)
                    {
                        // We have to stop
                        break;
                    }

                    Thread.Sleep(1);
                }
                while (clock.Milliseconds < endTime);
            }            
        }*/

        // Asynchronous update
        bool Task()
        {
            if (IsChanged || endTimeInvalidated)
            {
                // Preform deferred actions
                Perform();

                // Update duration
                endTime = 0;
                foreach (Timeline timeline in this)
                {
                    double currnet = timeline.Duration + timeline.BeginTime;
                    if (currnet > endTime) endTime = currnet;
                }
                endTimeInvalidated = false;
            }

            Update();
            if ((stopRequired)||(clock.Milliseconds > endTime))
            {
                // We have to stop
                return false;
            }

            return true;
        }
                
        // Updates the states of the animated 
        // parameters, if it is necessary
        void Update()
        {
            double milliseconds = clock.Milliseconds;

            for (int i = this.Count - 1; i >= 0; i--)
            {
                // It can be optimezed if we will check whether current 
                // time between StartTime and StartTime + Duration
                this[i].Apply(milliseconds);
            }
        }

        #endregion

        #region Backup

        /*/// <summary>
        /// Begins animation
        /// </summary>
        public void Begin()
        {
            clock.Start();
            StartAsyncUpdate();
        }

        /// <summary>
        /// Adds a timeline to this storyboard
        /// </summary>
        /// <param name="timeline">A timeline</param>
        public void Add(Timeline timeline)
        {
            if (timelines.Count == 0)
            {
                //beginTime = timeline.StartTime;
                duration = timeline.Duration;
            }
            else
            {
                // Updating startTime and duration
                //if (beginTime > timeline.StartTime) beginTime = timeline.StartTime;
                //if (beginTime + duration > timeline.StartTime + timeline.Duration)
                //    duration += beginTime + duration -
                //                timeline.StartTime - timeline.Duration;
            }
            timelines.Add(timeline);
        }

        /// <summary>
        /// Removes a timeline from this storyboard
        /// </summary>
        /// <param name="timeline">A timeline</param>
        public void Remove(Timeline timeline)
        {
            beginTime = Double.NaN;
            duration = Double.NaN;
            timelines.Remove(timeline);
        }

        /// <summary>
        /// Determines whether the collection contains the given timeline
        /// </summary>
        /// <param name="timeline">Timeline</param>
        /// <returns>True, if the given timeline exists, else false</returns>
        public bool Contains(Timeline timeline)
        {
            return timelines.Contains(timeline);
        }

        /// <summary>
        /// Updates the states of the animated 
        /// parameters, if it is necessary
        /// </summary>
        public void Update()
        {
            for (int i = timelines.Count - 1; i >= 0; i--)
            {
                Timeline timeline = timelines[i];
                // It can be optimezed if we will check whether current 
                // time between StartTime and StartTime + Duration
                timeline.Apply(clock.Milliseconds);
            }
        }*/

        #endregion
    }

    /// <summary>
    /// Storyboard updating delegate
    /// </summary>
    /// <returns>True if updating required, else task will be removed</returns>
    delegate bool StoryboardUpdateDelegate();
    
}
